{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **作者**:\tGaël Varoquaux\n",
    "\n",
    "[数学优化](http://en.wikipedia.org/wiki/Mathematical_optimization)处理寻找一个函数的最小值（最大值或零）的问题。在这种情况下，这个函数被称为*成本函数*，或*目标函数*，或*能量*。\n",
    "\n",
    "这里，我们感兴趣的是使用[scipy.optimize](http://docs.scipy.org/doc/scipy/reference/optimize.html#scipy.optimize)来进行黑盒优化： 我们不依赖于我们优化的函数的算术表达式。注意这个表达式通常可以用于高效的、非黑盒优化。\n",
    "\n",
    "> 先决条件\n",
    "- Numpy, Scipy\n",
    "- matplotlib\n",
    "\n",
    "---\n",
    "\n",
    "**也可以看一下: 参考**\n",
    "\n",
    "数学优化是非常 ... 数学的。如果你需要性能，那么很有必要读一下这些书:\n",
    "- [Convex Optimization](http://www.stanford.edu/~boyd/cvxbook/) Boyd and Vandenberghe (pdf版线上免费)。\n",
    "- [Numerical Optimization](http://users.eecs.northwestern.edu/~nocedal/book/num-opt.html), Nocedal and Wright。 关于梯度下降方法的详细参考。\n",
    "- [Practical Methods of Optimization](http://www.amazon.com/gp/product/0471494631/ref=ox_sc_act_title_1?ie=UTF8&smid=ATVPDKIKX0DER) Fletcher: 擅长挥手解释。\n",
    "\n",
    "**章节内容**\n",
    "- 了解你的问题\n",
    "    - 凸优化 VS 非凸优化\n",
    "    - 平滑问题和非平滑问题\n",
    "    - 嘈杂VS精确的成本函数\n",
    "    - 限制\n",
    "- 不同最优化方法的回顾\n",
    "    - 入门: 一维最优化\n",
    "    - 基于梯度的方法\n",
    "    - 牛顿和拟牛顿法\n",
    "    - 较少梯度方法\n",
    "    - 全局优化\n",
    "- 使用scipy优化的操作指南\n",
    "    - 选择一个方法\n",
    "    - 让你的优化器更快\n",
    "    - 计算梯度\n",
    "    - 虚拟练习\n",
    "- 特殊情境: 非线性最小二乘\n",
    "    - 最小化向量函数的范数\n",
    "    - 曲线拟合\n",
    "- 有限制的最优化\n",
    "    - 箱边界\n",
    "    - 通用限制\n",
    "    \n",
    "## 2.7.1 了解你的问题\n",
    "\n",
    "每个问题都是不相同。了解你的问题使你可以选择正确的工具。\n",
    "\n",
    "---\n",
    "**问题的维数**\n",
    "\n",
    "优化问题的规模非常好的由问题的维数来决定，即，进行搜索的标量变量的数量。\n",
    "\n",
    "---\n",
    "\n",
    "### 2.7.1.1 凸优化 VS 非凸优化\n",
    "\n",
    "**凸函数**:\n",
    "- $f$ 在它的所有切线之上。\n",
    "- 相应的, 对于两个点point A, B, f(C) 在线段[f(A), f(B])]之下, 如果 A < C < B\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_convex_1.png)\n",
    "\n",
    "**非凸函数**\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_convex_2.png)\n",
    "\n",
    "**最优化凸函数简单。最优化非凸函数可能非常困难。**\n",
    "\n",
    "> **注意**: 可以证明对于一个凸函数局部最小值也是全局最小值。然后，从某种意义上说，最小值是惟一的。\n",
    "\n",
    "### 2.7.1.2 平滑和非平滑问题\n",
    "\n",
    "**平滑函数**:\n",
    "\n",
    "梯度无处不在，是一个连续函数\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_smooth_1.png)\n",
    "\n",
    "**非平滑函数**:\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_smooth_2.png)\n",
    "\n",
    "**优化平滑函数更简单一些** (在黑盒最优化的前提是对的，此外[线性编程](http://en.wikipedia.org/wiki/Linear_programming)是一个非常高效处理分段线性函数的例子)。\n",
    "\n",
    "### 2.7.1.3 嘈杂 VS 精确成本函数\n",
    "\n",
    "有噪音 (blue) 和无噪音 (green) 函数\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_noisy_1.png)\n",
    "\n",
    "**噪音梯度**\n",
    "\n",
    "许多优化方法都依赖于目标函数的梯度。如果没有给出梯度函数，会从数值上计算他们，会产生误差。在这种情况下，即使目标函数没有噪音，基于梯度的最优化也可能是噪音最优化。\n",
    "\n",
    "### 2.7.1.4 限制\n",
    "\n",
    "基于限制的最优化\n",
    "\n",
    "这里是:\n",
    "\n",
    "$-1 < x_1 < 1$\n",
    "\n",
    "$-1 < x_2 < 1$\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_constraints_1.png)\n",
    "\n",
    "## 2.7.2 不同最优化方法的回顾\n",
    "\n",
    "### 2.7.2.1 入门: 一维最优化\n",
    "\n",
    "使用[scipy.optimize.brent()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.brent.html#scipy.optimize.brent) 来最小化一维函数。它混合抛物线近似与区间策略。\n",
    "\n",
    "**二元函数的Brent方法**: 在3次迭代后收敛, 因为，稍后二元近似精确了。\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_1d_optim_1.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_1d_optim_2.png)\n",
    "\n",
    "**非凸函数的Brent方法**: 注意最优化方法避免了局部最小值其实是因为运气。\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_1d_optim_3.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_1d_optim_4.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.6999999997839409"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from scipy import optimize\n",
    "def f(x):\n",
    "    return -np.exp(-(x - .7)**2)\n",
    "x_min = optimize.brent(f)  # 实际上在9次迭代后收敛!\n",
    "x_min "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-2.160590595323697e-10"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x_min - .7 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **注意**: Brent方法也可以用于*限制区间最优化*使用[scipy.optimize.fminbound()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fminbound.html#scipy.optimize.fminbound)\n",
    "\n",
    "> **注意**: 在scipy 0.11中, [scipy.optimize.minimize_scalar()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize_scalar.html#scipy.optimize.minimize_scalar) 给出了一个一维标量最优化的通用接口。\n",
    "\n",
    "### 2.7.2.2 基于梯度的方法\n",
    "\n",
    "#### 2.7.2.2.1 关于梯度下降的一些直觉\n",
    "\n",
    "这里我们关注**直觉**，不是代码。代码在后面。\n",
    "\n",
    "从根本上说，梯度下降在于在梯度方向上前进小步，即最陡峭梯度的方向。\n",
    "\n",
    "**固定步数梯度下降**\n",
    "\n",
    "**状况良好的二元函数。**\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_0.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_100.png)\n",
    "\n",
    "**状况糟糕的二元函数。**\n",
    "\n",
    "状况糟糕问题的梯度下降算法的核心问题是梯度并不会指向最低点。\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_2.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_102.png)\n",
    "\n",
    "我们可以看到非常各向异性 (状况糟糕) 函数非常难优化。\n",
    "\n",
    "---\n",
    "\n",
    "**带回家的信息**: 条件数和预条件化\n",
    "\n",
    "如果你知道变量的自然刻度，预刻度他们以便他们的行为相似。这与[预条件化](https://en.wikipedia.org/wiki/Preconditioner)相关。\n",
    "\n",
    "---\n",
    "\n",
    "并且，很明显采用大步幅是有优势的。这在梯度下降代码中使用[直线搜索](https://en.wikipedia.org/wiki/Line_search)。\n",
    "\n",
    "**适应步数梯度下降**\n",
    "\n",
    "\n",
    "状况良好的二元函数。\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_1.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_101.png)\n",
    "\n",
    "状况糟糕的二元函数。\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_3.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_103.png)\n",
    "\n",
    "状况糟糕的非二元函数。\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_4.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_104.png)\n",
    "\n",
    "状况糟糕的极端非二元函数。\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_5.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_105.png)\n",
    "\n",
    "函数看起来越像二元函数 (椭圆半圆边框线), 最优化越简单。\n",
    "\n",
    "#### 2.7.2.2.2 共轭梯度下降\n",
    "\n",
    "上面的梯度下降算法是玩具不会被用于真实的问题。\n",
    "\n",
    "正如从上面例子中看到的，简单梯度下降算法的一个问题是，它试着摇摆穿越峡谷，每次跟随梯度的方法，以便穿越峡谷。共轭梯度通过添加*摩擦力*项来解决这个问题: 每一步依赖于前两个值的梯度然后急转弯减少了。\n",
    "\n",
    "**共轭梯度下降**\n",
    "\n",
    "状况糟糕的非二元函数。\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_6.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_106.png)\n",
    "\n",
    "状况糟糕的极端非二元函数。\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_7.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_107.png)\n",
    "\n",
    "在scipy中基于共轭梯度下降方法名称带有‘cg’。最小化函数的简单共轭梯度下降方法是[scipy.optimize.fmin_cg()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_cg.html#scipy.optimize.fmin_cg):\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimization terminated successfully.\n",
      "         Current function value: 0.000000\n",
      "         Iterations: 13\n",
      "         Function evaluations: 120\n",
      "         Gradient evaluations: 30\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([ 0.99998968,  0.99997855])"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def f(x):   # The rosenbrock函数\n",
    "    return .5*(1 - x[0])**2 + (x[1] - x[0]**2)**2\n",
    "optimize.fmin_cg(f, [2, 2])  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这些方法需要函数的梯度。方法可以计算梯度，但是如果传递了梯度性能将更好:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimization terminated successfully.\n",
      "         Current function value: 0.000000\n",
      "         Iterations: 13\n",
      "         Function evaluations: 30\n",
      "         Gradient evaluations: 30\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([ 0.99999199,  0.99998336])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def fprime(x):\n",
    "    return np.array((-2*.5*(1 - x[0]) - 4*x[0]*(x[1] - x[0]**2), 2*(x[1] - x[0]**2)))\n",
    "optimize.fmin_cg(f, [2, 2], fprime=fprime)  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意函数只会评估30次，相对的没有梯度是120次。\n",
    "\n",
    "### 2.7.2.3 牛顿和拟牛顿法\n",
    "\n",
    "#### 2.7.2.3.1 牛顿法: 使用Hessian (二阶微分)\n",
    "\n",
    "[牛顿法](http://en.wikipedia.org/wiki/Newton%27s_method_in_optimization)使用局部二元近似来计算跳跃的方向。为了这个目的，他们依赖于函数的前两个导数*梯度*和[Hessian](http://en.wikipedia.org/wiki/Hessian_matrix)。\n",
    "\n",
    "**状况糟糕的二元函数:**\n",
    "\n",
    "注意，因为二元近似是精确的，牛顿法是非常快的。\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_8.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_108.png)\n",
    "\n",
    "**状况糟糕的非二元函数:**\n",
    "\n",
    "这里我们最优化高斯分布，通常在它的二元近似的下面。因此，牛顿法超调量并且导致震荡。\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_9.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_109.png)\n",
    "\n",
    "**状况糟糕的极端非二元函数:**\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_10.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_110.png)\n",
    "\n",
    "在scipy中, 最优化的牛顿法在[scipy.optimize.fmin_ncg()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_ncg.html#scipy.optimize.fmin_ncg)实现 (cg这里是指一个内部操作的事实，Hessian翻转, 使用共轭梯度来进行)。[scipy.optimize.fmin_tnc()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_tnc.html#scipy.optimize.fmin_tnc) 可以被用于限制问题，尽管没有那么多用途:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimization terminated successfully.\n",
      "         Current function value: 0.000000\n",
      "         Iterations: 9\n",
      "         Function evaluations: 11\n",
      "         Gradient evaluations: 51\n",
      "         Hessian evaluations: 0\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([ 1.,  1.])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def f(x):   # rosenbrock函数\n",
    "    return .5*(1 - x[0])**2 + (x[1] - x[0]**2)**2\n",
    "def fprime(x):\n",
    "    return np.array((-2*.5*(1 - x[0]) - 4*x[0]*(x[1] - x[0]**2), 2*(x[1] - x[0]**2)))\n",
    "optimize.fmin_ncg(f, [2, 2], fprime=fprime)    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意与共轭梯度（上面的）相比，牛顿法需要较少的函数评估，更多的梯度评估，因为它使用它近似Hessian。让我们计算Hessian并将它传给算法:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimization terminated successfully.\n",
      "         Current function value: 0.000000\n",
      "         Iterations: 9\n",
      "         Function evaluations: 11\n",
      "         Gradient evaluations: 19\n",
      "         Hessian evaluations: 9\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([ 1.,  1.])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def hessian(x): # Computed with sympy\n",
    "    return np.array(((1 - 4*x[1] + 12*x[0]**2, -4*x[0]), (-4*x[0], 2)))\n",
    "optimize.fmin_ncg(f, [2, 2], fprime=fprime, fhess=hessian)    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **注意**：在超高维，Hessian的翻转代价高昂并且不稳定 (大规模 > 250)。\n",
    "\n",
    "> **注意**：牛顿最优化算法不应该与基于相同原理的牛顿根发现法相混淆，[scipy.optimize.newton()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.newton.html#scipy.optimize.newton)。\n",
    "\n",
    "#### 2.7.2.3.2 拟牛顿方法: 进行着近似Hessian\n",
    "\n",
    "**BFGS**: BFGS (Broyden-Fletcher-Goldfarb-Shanno算法) 改进了每一步对Hessian的近似。\n",
    "\n",
    "**状况糟糕的二元函数:**\n",
    "\n",
    "在准确的二元函数中, BFGS并不像牛顿法那么快，但是还是很快。\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_11.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_111.png)\n",
    "\n",
    "**状况糟糕的非二元函数:**\n",
    "\n",
    "这种情况下BFGS比牛顿好, 因为它的曲度经验估计比Hessian给出的好。\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_12.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_112.png)\n",
    "\n",
    "**状况糟糕的极端非二元函数:**\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_13.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_113.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimization terminated successfully.\n",
      "         Current function value: 0.000000\n",
      "         Iterations: 16\n",
      "         Function evaluations: 24\n",
      "         Gradient evaluations: 24\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([ 1.00000017,  1.00000026])"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def f(x):   # rosenbrock函数\n",
    "    return .5*(1 - x[0])**2 + (x[1] - x[0]**2)**2\n",
    "def fprime(x):\n",
    "    return np.array((-2*.5*(1 - x[0]) - 4*x[0]*(x[1] - x[0]**2), 2*(x[1] - x[0]**2)))\n",
    "optimize.fmin_bfgs(f, [2, 2], fprime=fprime)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**L-BFGS**: 限制内存的BFGS介于BFGS和共轭梯度之间: 在非常高的维度 (> 250) 计算和翻转的Hessian矩阵的成本非常高。L-BFGS保留了低秩的版本。此外，scipy版本, [scipy.optimize.fmin_l_bfgs_b()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_l_bfgs_b.html#scipy.optimize.fmin_l_bfgs_b), 包含箱边界:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([ 1.00000005,  1.00000009]),\n",
       " 1.4417677473011859e-15,\n",
       " {'funcalls': 17,\n",
       "  'grad': array([  1.02331202e-07,  -2.59299369e-08]),\n",
       "  'nit': 16,\n",
       "  'task': 'CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL',\n",
       "  'warnflag': 0})"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def f(x):   # rosenbrock函数\n",
    "    return .5*(1 - x[0])**2 + (x[1] - x[0]**2)**2\n",
    "def fprime(x):\n",
    "    return np.array((-2*.5*(1 - x[0]) - 4*x[0]*(x[1] - x[0]**2), 2*(x[1] - x[0]**2)))\n",
    "optimize.fmin_l_bfgs_b(f, [2, 2], fprime=fprime) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **注意**：如果你不为L-BFGS求解器制定梯度，你需要添加approx_grad=1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.7.2.4 较少梯度方法\n",
    "\n",
    "#### 2.7.2.4.1 打靶法: Powell算法\n",
    "\n",
    "接近梯度方法\n",
    "\n",
    "**状态糟糕的二元函数:**\n",
    "\n",
    "Powell法对低维局部糟糕状况并不很敏感\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_14.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_114.png)\n",
    "\n",
    "**状况糟糕的极端非二元函数:**\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_16.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_116.png)\n",
    "\n",
    "#### 2.7.2.4.2 单纯形法: Nelder-Mead\n",
    "\n",
    "Nelder-Mead算法是对高维空间的对立方法的归纳。这个算法通过改进[单纯形](http://en.wikipedia.org/wiki/Simplex)来工作，高维空间间隔和三角形的归纳，包裹最小值。\n",
    "\n",
    "**长处**: 对噪音很强壮，他不依赖于计算梯度。因此，它可以在局部光滑的函数上工作，比如实验数据点，只要他显示了一个大规模的钟形行为。但是，它在光滑、非噪音函数上比基于梯度的方法慢。\n",
    "\n",
    "**状况糟糕的非二元函数:**\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_17.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_117.png)\n",
    "\n",
    "**状况糟糕的极端非二元函数:**\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_18.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_gradient_descent_118.png)\n",
    "\n",
    "在scipy中, [scipy.optimize.fmin()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin.html#scipy.optimize.fmin) 实现了Nelder-Mead法:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimization terminated successfully.\n",
      "         Current function value: 0.000000\n",
      "         Iterations: 46\n",
      "         Function evaluations: 91\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([ 0.99998568,  0.99996682])"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def f(x):   # rosenbrock函数\n",
    "    return .5*(1 - x[0])**2 + (x[1] - x[0]**2)**2\n",
    "optimize.fmin(f, [2, 2])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.7.2.5 全局最优化算法\n",
    "\n",
    "如果你的问题不允许惟一的局部最低点（很难测试除非是凸函数），如果你没有先前知识来让优化起点接近答案，你可能需要全局最优化算法。\n",
    "\n",
    "#### 2.7.2.5.1 暴力: 网格搜索\n",
    "\n",
    "[scipy.optimize.brute()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.brute.html#scipy.optimize.brute)在 函数网格内来评价函数，根据最小值返回参数。参数由[numpy.mgrid](http://docs.scipy.org/doc/numpy/reference/generated/numpy.mgrid.html#numpy.mgrid)给出的范围来指定。默认情况下，每个方向进行20步:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 1.00001462,  1.00001547])"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def f(x):   # rosenbrock函数\n",
    "    return .5*(1 - x[0])**2 + (x[1] - x[0]**2)**2\n",
    "optimize.brute(f, ((-1, 2), (-1, 2)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.7.3 使用scipy优化的现实指南\n",
    "\n",
    "### 2.7.3.1 选择一个方法\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_compare_optimizers_1.png)\n",
    "\n",
    "---\n",
    "\n",
    "**没有关于梯度的知识:**\n",
    " \t\n",
    "- 一般来说，倾向于BFGS ([scipy.optimize.fmin_bfgs()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_bfgs.html#scipy.optimize.fmin_bfgs)) 或 L-BFGS ([scipy.optimize.fmin_l_bfgs_b()]()), 即使你有大概的数值梯度\n",
    "- 在状况良好的问题上，Powell ([scipy.optimize.fmin_powell()]()) 以及 Nelder-Mead ([scipy.optimize.fmin()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin.html#scipy.optimize.fmin)), 都是在高维上效果良好的梯度自有的方法，但是 ，他们无法支持状况糟糕的问题。\n",
    "\n",
    "---\n",
    "\n",
    "---\n",
    "\n",
    "**有关于梯度的知识:**\n",
    " \t\n",
    "- BFGS ([scipy.optimize.fmin_bfgs()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_bfgs.html#scipy.optimize.fmin_bfgs)) 或 L-BFGS ([scipy.optimize.fmin_l_bfgs_b()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_l_bfgs_b.html#scipy.optimize.fmin_l_bfgs_b))。\n",
    "- BFGS的计算开支要大于L-BFGS, 它自身也比共轭梯度法开销大。另一方面，BFGS通常比CG（共轭梯度法）需要更少函数评估。因此，共轭梯度法在优化计算量较少的函数时比BFGS更好。\n",
    "\n",
    "---\n",
    "\n",
    "---\n",
    "**带有Hessian**:\n",
    "\n",
    "- 如果你可以计算Hessian, 推荐牛顿法 ([scipy.optimize.fmin_ncg()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_ncg.html#scipy.optimize.fmin_ncg))。\n",
    "\n",
    "**如果有噪音测量**:\n",
    " \t\n",
    "使用Nelder-Mead ([scipy.optimize.fmin()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin.html#scipy.optimize.fmin)) 或者 Powell ([scipy.optimize.fmin_powell()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_powell.html#scipy.optimize.fmin_powell))。\n",
    "\n",
    "### 2.7.3.2 让优化器更快\n",
    "\n",
    "- 选择正确的方法 (见上面), 如果可以的话，计算梯度和Hessia。\n",
    "- 可能的时候使用[preconditionning](http://en.wikipedia.org/wiki/Preconditioner)。\n",
    "- 聪明的选择你的起点。例如，如果你正在运行许多相似的优化，那么在其他结果上软启动。\n",
    "- 如果你不需要准确，那么请放松并容忍\n",
    "\n",
    "### 2.7.3.3 计算梯度\n",
    "\n",
    "计算梯度甚至是Hessians的努力, 是枯燥的但是也是值得的。使用[Sympy](http://www.scipy-lectures.org/packages/sympy.html#sympy)来进行象征计算将非常方便。\n",
    "\n",
    "优化不能很好收敛的一个来源是计算梯度过程的人为错误。你可以用[scipy.optimize.check_grad()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.check_grad.html#scipy.optimize.check_grad)来检查一下梯度是否正确。它返回给出的梯度与计算的梯度之间差异的基准:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2.384185791015625e-07"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "optimize.check_grad(f, fprime, [2, 2])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "也看一下[scipy.optimize.approx_fprime()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.approx_fprime.html#scipy.optimize.approx_fprime)找一下你的错误。\n",
    "#### 2.7.3.4 合成练习\n",
    "\n",
    "**练习: 简单的 (?) 二次函数**\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_exercise_ill_conditioned_1.png)\n",
    "\n",
    "用K[0]作为起始点优化下列函数:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "np.random.seed(0)\n",
    "K = np.random.normal(size=(100, 100))\n",
    "\n",
    "def f(x):\n",
    "    return np.sum((np.dot(K, x - 1))**2) + np.sum(x**2)**2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "计时你的方法。找到最快的方法。为什么BFGS不好用了?\n",
    "\n",
    "**练习：局部扁平最小化**\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_exercise_flat_minimum_0.png)\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_exercise_flat_minimum_1.png)\n",
    "\n",
    "考虑一下函数$exp(-1/(.1*x^2 + y^2)$。这个函数在（0，0）存在一个最小值。从起点（1，1）开始，试着在$1e-8$达到这个最低点。\n",
    "\n",
    "## 2.7.4 特殊案例: 非线性最小二乘\n",
    "\n",
    "### 2.7.4.1 最小化向量函数的基准\n",
    "\n",
    "最小二乘法，向量函数基准值的最小化，有特定的结构可以用在[scipy.optimize.leastsq()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.leastsq.html#scipy.optimize.leastsq)中实现的[Levenberg–Marquardt 算法](https://en.wikipedia.org/wiki/Levenberg-Marquardt_algorithm)。\n",
    "\n",
    "让我们试一下最小化下面向量函数的基准:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([ 0.        ,  0.11111111,  0.22222222,  0.33333333,  0.44444444,\n",
       "         0.55555556,  0.66666667,  0.77777778,  0.88888889,  1.        ]), 2)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def f(x):\n",
    "    return np.arctan(x) - np.arctan(np.linspace(0, 1, len(x)))\n",
    "x0 = np.zeros(10)\n",
    "optimize.leastsq(f, x0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这用了67次函数评估(用'full_output=1'试一下)。如果我们自己计算基准并且使用一个更好的通用优化器（BFGS）会怎么样:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimization terminated successfully.\n",
      "         Current function value: 0.000000\n",
      "         Iterations: 11\n",
      "         Function evaluations: 144\n",
      "         Gradient evaluations: 12\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([ -7.44987291e-09,   1.11112265e-01,   2.22219893e-01,\n",
       "         3.33331914e-01,   4.44449794e-01,   5.55560493e-01,\n",
       "         6.66672149e-01,   7.77779758e-01,   8.88882036e-01,\n",
       "         1.00001026e+00])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def g(x):\n",
    "    return np.sum(f(x)**2)\n",
    "optimize.fmin_bfgs(g, x0)   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "BFGS需要更多的函数调用，并且给出了一个并不精确的结果。\n",
    "\n",
    "注意只有当输出向量的维度非常大，比需要优化的函数还要大，`leastsq`与BFGS相类比才是有趣的。\n",
    "\n",
    "如果函数是线性的，这是一个线性代数问题，应该用[scipy.linalg.lstsq()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.lstsq.html#scipy.linalg.lstsq)解决。\n",
    "\n",
    "### 2.7.4.2 曲线拟合\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_curve_fit_1.png)\n",
    "\n",
    "最小二乘问题通常出现在拟合数据的非线性拟合时。当我们自己构建优化问题时，scipy提供了这种目的的一个帮助函数: [scipy.optimize.curve_fit()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html#scipy.optimize.curve_fit):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([ 1.50600889,  0.98754323]), array([[ 0.00030286, -0.00045233],\n",
       "        [-0.00045233,  0.00098838]]))"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def f(t, omega, phi):\n",
    "    return np.cos(omega * t + phi)\n",
    "x = np.linspace(0, 3, 50)\n",
    "y = f(x, 1.5, 1) + .1*np.random.normal(size=50)\n",
    "optimize.curve_fit(f, x, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**练习**\n",
    "\n",
    "用omega = 3来进行相同的练习。困难是什么？\n",
    "\n",
    "## 2.7.5 有限制条件的优化\n",
    "\n",
    "### 2.7.5.1 箱边界\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_constraints_2.png)\n",
    "\n",
    "箱边界是指限制优化的每个函数。注意一些最初不是写成箱边界的问题可以通过改变变量重写。\n",
    "\n",
    "- [scipy.optimize.fminbound()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fminbound.html#scipy.optimize.fminbound)进行一维优化\n",
    "- [scipy.optimize.fmin_l_bfgs_b()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_l_bfgs_b.html#scipy.optimize.fmin_l_bfgs_b)带有边界限制的quasi-Newton方法:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([ 1.5,  1.5]),\n",
       " 1.5811388300841898,\n",
       " {'funcalls': 12,\n",
       "  'grad': array([-0.94868331, -0.31622778]),\n",
       "  'nit': 2,\n",
       "  'task': 'CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL',\n",
       "  'warnflag': 0})"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def f(x):\n",
    "    return np.sqrt((x[0] - 3)**2 + (x[1] - 2)**2)\n",
    "optimize.fmin_l_bfgs_b(f, np.array([0, 0]), approx_grad=1, bounds=((-1.5, 1.5), (-1.5, 1.5)))   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.7.5.2 通用限制\n",
    "\n",
    "相等和不相等限制特定函数: f(x) = 0 and g(x)< 0。\n",
    "\n",
    "![](http://www.scipy-lectures.org/_images/plot_non_bounds_constraints_1.png)\n",
    "\n",
    "- [scipy.optimize.fmin_slsqp()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_slsqp.html#scipy.optimize.fmin_slsqp) 序列最小二乘程序: 相等和不相等限制:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimization terminated successfully.    (Exit mode 0)\n",
      "            Current function value: 2.47487373504\n",
      "            Iterations: 5\n",
      "            Function evaluations: 20\n",
      "            Gradient evaluations: 5\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([ 1.25004696,  0.24995304])"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def f(x):\n",
    "    return np.sqrt((x[0] - 3)**2 + (x[1] - 2)**2)\n",
    "\n",
    "def constraint(x):\n",
    "    return np.atleast_1d(1.5 - np.sum(np.abs(x)))\n",
    "\n",
    "optimize.fmin_slsqp(f, np.array([0, 0]), ieqcons=[constraint, ])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- [scipy.optimize.fmin_cobyla()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_cobyla.html#scipy.optimize.fmin_cobyla)通过线性估计的限定优化：只有不相等限制："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 1.25009622,  0.24990378])"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "optimize.fmin_cobyla(f, np.array([0, 0]), cons=constraint)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上面这个问题在统计中被称为[Lasso](http://en.wikipedia.org/wiki/Lasso_(statistics)#LASSO_method)问题, 有许多解决它的高效方法 (比如在[scikit-learn](http://scikit-learn.org/)中)。一般来说，当特定求解器存在时不需要使用通用求解器。\n",
    "\n",
    "**拉格朗日乘子法**\n",
    "\n",
    "如果你有足够的数学知识，许多限定优化问题可以被转化为非限定性优化问题，使用被称为拉格朗日乘子法的数学技巧。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
